package org.fjsei.yewu.config.datasource;

import com.zaxxer.hikari.HikariDataSource;
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
import net.ttddyy.dsproxy.support.ProxyDataSource;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.fjsei.yewu.jpa.CustomRepositoryFactoryBean;
import org.fjsei.yewu.jpa.ExtendedJpaRepositoryImpl;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.dialect.H2Dialect;
import org.infinispan.hibernate.cache.v62.InfinispanRegionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.repository.config.BootstrapMode;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;

import jakarta.annotation.Resource;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;

import static org.springframework.data.repository.config.BootstrapMode.LAZY;

/*
 阿里云DMS（数据管理）推出了跨数据库实例查询服务，一条SQL完成跨数据库实例Join查询?
*/

//启动失败hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags看https://blog.csdn.net/danchaofan0534/article/details/53873207
//1.将List变为Set　  .fetch=FetchType.LAZY　3.@Fetch(FetchMode.SUBSELECT)   4.不是JPA规范@IndexColumn唯一性索引;
//一对多或多对多的多方数据存容器类如Set、List、Map;
//解决了问题：@ManyToMany或@OneToMany的Many多的那一方，一定用Set容器来存放，而不能用List集合。


//我的主库,  basePackages子目录可以
//多个数据源数据库，@EnableJpaRepositories不是单个StartApplication上面直接注解，需要独立多个注解。
//依靠PojoXxxRepository所在的目录来区分到底是JPA还Elasticsearch或MongoDb存储库，且和POJO类所在目录没关系。
//配置repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class用于替换标准的org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean

/** 正是因为多个数据库才搞的复杂配置，默认情况可大大简化！
 * 把@EnableJpaRepositories放在这里，而不是直接给StartApplication注解，多个数据库的情况是这丫。
 * @Primary 主数据库数据源：只能一个，没有特殊声明的都会用@Primary数据库。
 * JPA实体映射才需要的：
 * 除了Sei主数据库，其他数据库保存需要emXxx.joinTransaction();入口顶层函数@Transactional注解配合。
 * 对于比如camunda服务专用数据库那种的就不需要额外做定义了。
 * 底下repositoryBaseClass = ExtendedJpaRepositoryImpl.class是针对CRDB数据库的upsert and upsertAll优化配置,不是这种数据库用不了。
 * 去掉repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class,报错PropertyReferenceException: No property 'findAllNc' found for type 'Unit'!淘汰用法改Slice;
 * 传递链：CustomRepositoryFactoryBean.createRepositoryFactory:>CustomRepositoryFactory.RepositoryBaseClass===CustomRepositoryImpl:>QuerydslNcExecutorImpl;起因是count()耗时findAllNc。
 * 改配置EnableJpaRepositories(bootstrapMode：测试，生产环境?。默认DEFAULT最早就得载入数据库，DEFERRED耗时的数据库加载时机往后推，LAZY推到最迟才做；启动快用LAZY。
 * 默认单数据库没定做可不用加@EnableJpaRepositories{} repositoryBaseClass=是底层定制入口，而repositoryFactoryBeanClass=是更加底1层级的定做入口。
 * 而｛｝entityManagerFactoryRef是数据库配置的连接点。｛｝basePackages定义单个数据库的@Entity实体表包class目录。{}transactionManagerRef配置第三方分布式XA事务系统。
 * MySql对UUID支持不好没法旧的代码做实体关联，Entity的ID类型改动对代码影响面非常深远！
 * */

@Configuration
@EnableJpaRepositories(
        repositoryBaseClass = ExtendedJpaRepositoryImpl.class,
        repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class,
        bootstrapMode= BootstrapMode.LAZY,
        entityManagerFactoryRef = "entityManagerFactorySei",
        basePackages ={"org.fjsei.yewu.repository", "md"}
    )
@EnableElasticsearchRepositories(basePackages = {"org.fjsei.yewu.index"})
public class SeiConfig {

    @Value("${app.datasource.sei.dialect:org.hibernate.dialect.CockroachDialect}")
    private  String  dialect="";
//    @Value("${spring.jpa.properties.hibernate.show_sql:false}")
//    private boolean  isShowSql;

    @Resource
    private JpaProperties jpaProperties;

    //只能一个 是有@Primary 注释的 数据源。主数据库有优待。
    @Primary
    @Bean(name = "dataSourcePropertiesSei")
    @Qualifier("dataSourcePropertiesSei")
    @ConfigurationProperties(prefix="app.datasource.sei")
    public DataSourceProperties dataSourcePropertiesSei() {
        return new DataSourceProperties();
    }

    /**CRDB增加
     * Hikari连接池是Spring Boot 2.0+默认也是官方推荐的连接池，适合高并发下的业务场景。DBCP、C3P0、Druid 等
     * 数据库连接池连接超时异常断开的问题。提出以下解决思路  1.延长maxLifetime大小  默认30分钟 ，如果设置为0，表示存活时间无限大。
     * 程序显式调用 HikariDataSource#evictConnection ,HikariPool#evict Connection
     * */
    @Bean
    @ConfigurationProperties("spring.datasource.hikari")
    public HikariDataSource hikariDataSource() {
        HikariDataSource hikariDataSource= dataSourcePropertiesSei()
                .initializeDataSourceBuilder()
                .type(HikariDataSource.class)
                .build();
        //【java22:未来虚拟线程】Project loom ，java版本 异步:根本不需要线程池了。
        hikariDataSource.setPoolName("seiDBpool");
        hikariDataSource.setLeakDetectionThreshold(5000);
        hikariDataSource.setIdleTimeout(10000);
        hikariDataSource.setConnectionTimeout(30000);
        hikariDataSource.setValidationTimeout(3000);
        hikariDataSource.setMaxLifetime(300000);
        /* 具体底层数据库相关！
        hikariDataSource.setUsername(null);      //"flow" bpm schema?
        hikariDataSource.setPassword(null);
        */
        return hikariDataSource;
    }

    //区分DataSource接口Bean/Class实例的多个不同名字用@Qualifier;
    //类似的依据名称标识@Qualifier("dataSourcePropertiesSei") DataSourceProperties配置文件读取可配置字段;
    //针对整个连接池整体的: @Bean(name = "xxxDataSource",destroyMethod = "close") 旧的返回DataSource类型;
    @Primary
    @Bean(name = "seiDataSource", destroyMethod = "close")
    @Qualifier("seiDataSource")
    public ProxyDataSource seiDataSource() {
        return ProxyDataSourceBuilder
                .create(hikariDataSource())
                .name("SQL-Trace")
                .asJson()
                .logQueryBySlf4j(SLF4JLogLevel.TRACE, "cn.fjsei.SQL_TRACE")
                .build();
    }


    /**
     可能用JdbcTemplate，位于和数据库直接交互的jdbc层次的；
     直接用最底下一层jdbc的最原始模式： JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
     JDBC也能做 @EnableJdbcRepositories @Repository CrudRepository<Account, Long>，但是高级JPA功能没有;
     */
    @Bean(name = "seiJdbcTemplate")
    @Qualifier("seiJdbcTemplate")
    public JdbcTemplate seiJdbcTemplate(@Qualifier("seiDataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }


    @Bean
    public PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor() {
        return new PersistenceExceptionTranslationPostProcessor();
    }

    @Primary
    @Bean(name = "entityManagerSei")
    public EntityManager entityManager() {
        return Objects.requireNonNull(entityManagerFactorySei().getObject()).createEntityManager();
    }


    /** 依照DataSource指示才去生成中间的： LocalContainerEntityManagerFactoryBean 它才是发动机：它负责生成EntityManager。
     * 设置实体类所在位置
     */
/*    @Primary
    @Bean(name = "entityManagerFactorySei")
    //@DependsOn("transactionManager")
    //@DependsOn("transactionManager")
    public LocalContainerEntityManagerFactoryBean entityManagerFactorySei(EntityManagerFactoryBuilder builder) {
        return builder
            .dataSource(seiDataSource)
            .packages("org.fjsei.yewu.repository","md")
            .persistenceUnit("seiPersistenceUnit")
         //.properties(getVendorProperties())　　实际环境Oracle连接特别得慢！　本地H2测试库连接很快。
            .build();

        //必须在@EnableJpaRepositories里头注解  "org.fjsei.yewu.entity.sei"
    }*/

/*    JPA使用的
    //@Primary
    @Bean(name = "transactionManagerBar")
    public PlatformTransactionManager transactionManagerSei(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(entityManagerFactorySei(builder).getObject());
    }*/
    //不需要直接用HibernateTransactionManager， 最好是用JpaTransactionManager

    @Bean
    public PlatformTransactionManager transactionManager(@Autowired EntityManagerFactory emf) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf);
        transactionManager.setJpaDialect(new HibernateJpaDialect());
        return transactionManager;
    }

    //独立数据库，不支持跨越多数据库的事务。 实际构造 EntityManagerFactory
    @Primary
    @Bean(name = "entityManagerFactorySei")
    public LocalContainerEntityManagerFactoryBean entityManagerFactorySei() {
        LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
        emf.setDataSource(seiDataSource());
        emf.setPackagesToScan("md","org.fjsei.yewu.repository");     //不能省略
        /*从跟踪AbstractEntityManagerFactoryBean.jpaPropertyMap:发现其他数据库的配置29个属性，我这里才有14属性的。导致查询缓冲实际没有开动问题！
        所以必须添加上低下这句：把基本的共同配置属性也带上！
        "hibernate.cache.infinispan.cfg" -> "infinispan-dev.xml"
        "hibernate.cache.region.factory_class" -> "infinispan"
         但是导致spring.jpa.properties.hibernate.search.backend.hosts: localhost传递给HibernateSearch报错 hibernate.search配置被冲没掉？
        HibernateSearch应该走 hibernate.properties  pesistence.xml 去配置ES服务端,不要放"spring.jpa.properties.hibernate..."这。
        * */
        emf.setJpaPropertyMap(jpaProperties.getProperties());
        emf.setJpaProperties(jpaVendorProperties());
        emf.setJpaVendorAdapter(jpaVendorAdapter());
        //emf.getJpaPropertyMap().put(Environment.DIALECT, CockroachDialect.class.getName());
        emf.setPersistenceUnitName("seiPersistenceUnit");
        return emf;
    }
    /** 和application.yml里面相同配置比：这里会覆盖对方的；
     * 这个覆盖掉jpaProperties.getProperties()相同属性。
     * */
    private Properties jpaVendorProperties() {
        return new Properties() {
            {
                setProperty(Environment.GENERATE_STATISTICS, Boolean.TRUE.toString());
                setProperty(Environment.LOG_SESSION_METRICS, Boolean.TRUE.toString());    //FALSE无法输出慢sql日志
                //JPA慢SQL侦查 SlowQuery: 277 milliseconds. SQL: 'HikariProxyPreparedStatement [； ？277=可能不包含fetching的ms? hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS
//                setProperty(Environment.LOG_SLOW_QUERY, "100");     //不添加不行！其它的3个数据源直接会采用application.yml配置LOG_QUERIES_SLOWER_THAN_MS。

                setProperty(Environment.USE_MINIMAL_PUTS, Boolean.TRUE.toString());
               //CRDB例子引入: setProperty(Environment.USE_SECOND_LEVEL_CACHE, Boolean.FALSE.toString());
               //CRDB例子引入: setProperty(Environment.CACHE_REGION_FACTORY, NoCachingRegionFactory.class.getName());
                setProperty(Environment.CACHE_REGION_FACTORY, InfinispanRegionFactory.class.getName());
                setProperty(Environment.STATEMENT_BATCH_SIZE, "250");
                setProperty(Environment.BATCH_VERSIONED_DATA, Boolean.TRUE.toString());
                setProperty(Environment.ORDER_INSERTS, Boolean.TRUE.toString());
                setProperty(Environment.ORDER_UPDATES, Boolean.TRUE.toString());
                setProperty(Environment.FORMAT_SQL, Boolean.FALSE.toString());       //和application.yml里面相同配置比：这里还不如对方的优先；

                // Mutes Postgres JPA Error (Method org.postgresql.jdbc.PgConnection.createClob() is not yet implemented).
                setProperty(Environment.NON_CONTEXTUAL_LOB_CREATION, Boolean.TRUE.toString());
                /* 具体底层数据库相关！ "hibernate.dialect"   H2Dialect.class.getName()
                    spring.jpa.database-platform=
                    spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.Oracle12cDialect
                datasource:sei: org.hibernate.dialect.MySQLDialect改成.MySQL5Dialect才行,去掉driver-class-name=‘’;
                */
                setProperty(Environment.DIALECT, dialect);
                //HBM2DDL_CREATE_NAMESPACES = "hibernate.hbm2ddl.create_namespaces";
            }
        };
    }

    /**物理数据库：类型这里已经配置，
     * hikari.pool或 ttddyy.dsproxy 记住? postgresql.util.PSQLException: Connection to 192.168.171.3:26257
     * */
    private JpaVendorAdapter jpaVendorAdapter() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        //可定制 :生产新字段新表
        vendorAdapter.setGenerateDdl(jpaProperties.isGenerateDdl());
        //所有sql都进入日志:太多了  true  false
//        vendorAdapter.setShowSql(isShowSql);     //和application.yml里面相同配置比：这里比对方的设置优先；
        /* 具体底层数据库相关
        vendorAdapter.setDatabasePlatform(CockroachDialect.class.getName());
        vendorAdapter.setDatabase(Database.POSTGRESQL); */
        return vendorAdapter;
    }
}



/* 最早是
@EnableTransactionManagement
@EnableJpaRepositories(
    entityManagerFactoryRef = "entityManagerFactorySei",
    transactionManagerRef = "transactionManager",
    repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class,
    basePackages = {"org.fjsei.yewu.repository","md"},
    excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = ElasticsearchRepository.class))
@EnableElasticsearchRepositories(
        basePackages = {"org.fjsei.yewu.index.sei"},
        includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = ElasticsearchRepository.class))
 */


/*默认数据源配置，单个数据库的：不需要@Configuration做配置也可以！
spring
  datasource:
    url: jdbc:postgresql://localhost:26257/roach_data?sslmode=disable
    driver-class-name:
不管是JPA还是JDBC等都实现自接口 PlatformTransactionManager 如果你添加的是 spring-boot-starter-jdbc 依赖，框架会默认注入DataSourceTransactionManager实例。
如果你添加的是 spring-boot-starter-data-jpa 依赖，框架会默认注入 JpaTransactionManager实例:两者都不是jakarta.transaction.TransactionManager的。
Out of the box, the process engine supports integration with Spring and JTA transaction management. :
1,Section on Spring Transaction Management  从Spring自带的。
2,Section on JTA Transaction Management 第三方或容器介入的。javaEE?
这里的jakarta.transaction.TransactionManager是只有javaEE应用服务器才提供{分布式事务框架}，Spring默认的没有带。而Infinispan也搞自身的TransactionManager啥。
【注意】包名不一样的: jakarta.transaction.t 和org/springframework/transaction/TransactionManager不是一个东西的。
public class ProfiledemoApplication implements TransactionManagementConfigurer {
    @Bean(name = "txManager1")
    public PlatformTransactionManager txManager(DataSource dataSource) {
    @Bean(name = "txManager2")
    public PlatformTransactionManager txManager2(EntityManagerFactory factory) {
    // 实现接口 TransactionManagementConfigurer 方法，其返回值代表在拥有多个事务管理器的情况下默认使用的事务管理器
    @Override
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return txManager2;    =各自管理自己的？  ？分布式事务(跨越多数据库的情况)管理谁搞。
    }
@Transactional 只能被应用到public方法上；
【结论】bpm流程引擎独立数据库或微服务搞出问题：还是需要需要我们的分布式事务来把控。不同数据库的不一致性如何解决。 Seata阿里巴巴分布式事务解决方案 @GlobalTransactional
*/


/*
 跨实例SQL查询==数据库网关是DMS刚推出的新特性,阿里云DMS发布 https://www.sohu.com/a/299468414_612370
 云数据库，分布式数据库服务，Amazon的Aurora，阿里云的PolarDB等 https://www.cnblogs.com/cchust/p/11366175.html
 底层的Jdbc数据源配置类;这个也必须配置，否则错误非常隐蔽与深处。
 * 关系数据库数据源+Jdbc配置部分:
 * @Primary 主数据库数据源：只能一个，没有特殊声明的都会用@Primary数据库。
*/
